package utils

import (
	"archive/tar"
	"bufio"
	"bytes"
	"errors"
	"fmt"
	"github.com/klauspost/pgzip"
	"io"
	"io/ioutil"
	"os"
	"path"
	"path/filepath"
	"runtime"
	"strings"
	"time"
)

var (
	// .gz的header byte
	gzHeaderBytesTag  = []byte{0x1f, 0x8b}
	tarHeaderBytesTag = []byte{0x75, 0x73, 0x74, 0x61, 0x72} // at offset 257
)

const (
	nullType  = ""
	tarType   = "tar"
	gzType    = "gz"
	targzType = "tar.gz"
)

type unCompress struct {
	error          error         // Error信息
	compressType   string        // 压缩包类型
	compressReader *bufio.Reader // Reader
	deferFunc      func()
}

func InitUnCompress(sourceReader io.Reader) (obj *unCompress) {
	obj = new(unCompress)
	obj.deferFunc = func() {}
	// 初始化检查压缩包类型
	// 新建bufioReader
	reader := bufio.NewReader(sourceReader)
	// 判断压缩包类型
	compressType, err := getCompressType(reader, 0)
	if err != nil {
		obj.error = err
		return
	}
	// 是个gz则读取一下
	if compressType == gzType {
		gzReader, err := pgzip.NewReader(reader)
		if err != nil {
			obj.error = err
			return
		}
		obj.deferFunc = func() {
			gzReader.Close()
		}
		reader = bufio.NewReader(gzReader)
	}
	// 判断是否是个tar
	compressType2, err := getCompressType(reader, 257)
	if err != nil {
		obj.error = err
		return
	}
	if compressType2 == tarType && compressType == gzType {
		compressType = targzType
	} else if compressType2 == tarType {
		compressType = tarType
	}
	if compressType == nullType {
		obj.error = errors.New("unsupported compress")
		return
	}

	obj.compressType = compressType
	obj.compressReader = reader
	return
}

func (p *unCompress) GetCompressType() (compressType string, err error) {
	if p.error != nil {
		err = p.error
		return
	}
	defer p.deferFunc()
	return p.compressType, nil
}

// UnCompress 正式的解压操作，返回解压的路径和错误信息
// 用完记得删除压缩文件和解压目录
func (p *unCompress) UnCompress(destDir string) (destPath string, err error) {
	if p.error != nil {
		err = p.error
		return
	}
	defer p.deferFunc()
	// 确认解压目录
	destDir, err = makeDestDir(destDir)
	if err != nil {
		p.error = err
		return
	}
	// 判断类型，执行解压
	if p.compressType == tarType || p.compressType == targzType {
		destPath, err = unTar(p.compressReader, destDir)
		p.error = err
		return
	}

	// 如果不是tar或者tar.gz 那么将它保存到磁盘上
	fPath := filepath.Join(destDir, sanitizeArchiveName(path.Base("unknown-compress")))
	// 创建文件
	file, err := os.Create(fPath)
	if err != nil {
		p.error = err
		return
	}
	defer file.Close()

	// copy
	if _, err = io.Copy(file, p.compressReader); err != nil {
		p.error = err
		return
	}
	return destDir, nil

}

// makeDestDir 创建解压目录
func makeDestDir(destDir string) (newDestDir string, err error) {
	newDestDir = destDir
	// 判断解压路径是否指定了
	if len(destDir) == 0 {
		newDestDir, err = ioutil.TempDir(os.TempDir(), "uncompress-")
	}
	//  确认destDir 目录是否存在，不存在则创建
	err = os.MkdirAll(newDestDir, 0740)
	return
}

func unTar(compressReader io.Reader, destDir string) (string, error) {
	tarReader := tar.NewReader(compressReader)
	// 遍历迭代压缩包
	rootDir := destDir
	for {
		itemArchive, err := tarReader.Next()
		// 遍历结束
		if err == io.EOF {
			break
		}
		if err != nil {
			return rootDir, err
		}
		// 跳过 pax_global_header
		if itemArchive.Name == "pax_global_header" {
			continue
		}
		// 组装包文件路径
		fPath := filepath.Join(destDir, sanitizeArchiveName(itemArchive.Name))
		// 如果包文件是个目录则创建
		if itemArchive.FileInfo().IsDir() {
			if rootDir == destDir {
				rootDir = fPath
			}
			if err = os.MkdirAll(fPath, os.FileMode(itemArchive.Mode)); err != nil {
				return rootDir, err
			}
			continue
		}
		// 处理包文件
		_, err = unTarFile(itemArchive, tarReader, fPath, rootDir)
		if err != nil {
			return rootDir, err
		}
	}
	return rootDir, nil
}

func unTarFile(itemArchive *tar.Header, tarReader *tar.Reader, fPath, rootDir string) (string, error) {
	// 创建包文件
	file, err := os.Create(fPath)
	if err != nil {
		return rootDir, err
	}
	defer file.Close()

	// 处理包文件的权限
	if err = file.Chmod(os.FileMode(itemArchive.Mode)); err != nil {
		return rootDir, fmt.Errorf("set archive file permissions err %q: %#v", file.Name(), err)
	}

	// 处理包文件时间
	if err = os.Chtimes(file.Name(), time.Now(), itemArchive.ModTime); err != nil {
		return rootDir, fmt.Errorf("set archive file atime and mtime err %q: %#v", file.Name(), err)
	}

	// 拷贝
	_, err = io.Copy(file, tarReader)
	if err != nil {
		return rootDir, fmt.Errorf("copy archive file err %q: %#v", file.Name(), err)
	}

	return rootDir, nil
}

// Sanitizes 处理包中文件名称，以避免在取消归档时覆盖敏感系统文件
func sanitizeArchiveName(name string) string {
	// 忽略Windows中的卷驱动器标签
	if len(name) > 1 && name[1] == ':' && runtime.GOOS == "windows" {
		name = name[2:]
	}
	name = filepath.Clean(name)
	name = filepath.ToSlash(name)
	for strings.HasPrefix(name, "../") {
		name = name[3:]
	}
	return name
}

// getCompressType 获取压缩包类型
func getCompressType(reader *bufio.Reader, offset int) (string, error) {
	hBytes, err := reader.Peek(offset + 6)
	if err != nil {
		if err == io.EOF {
			err = nil
		}
		return nullType, err
	}
	magic := hBytes[offset : offset+6]
	if bytes.Equal(tarHeaderBytesTag, magic[0:5]) {
		return tarType, nil
	}
	if bytes.Equal(gzHeaderBytesTag, magic[0:2]) {
		return gzType, nil
	}
	return nullType, nil
}
